#include <Rz/Range.h>

#include "GLRenderDevice.h"
#include "GLVertexBuffer.h"
#include "GLIndexBuffer.h"
#include "GLVertexArray.h"
#include "GLEffect.h"

namespace Rz { namespace Graphics { namespace Render { namespace Libraries { namespace OpenGL {

bool GLRenderDevice::Init()
{
	// Depth
	GL::Enable(GL_DEPTH_TEST);
	GL::DepthFunc(GL_LEQUAL);
 	GL::DepthMask(GL_TRUE);

	// Face culling
	GL::Enable(GL_CULL_FACE);
	GL::FrontFace(GL_CCW);

	// Blend mode - TODO
	GL::Disable(GL_BLEND);

	return true;
}

void GLRenderDevice::End()
{

}

void GLRenderDevice::Clear(ClearOptions clearMode, const Math::Vector4& color) 
{
	GLenum clearBits = 0;

	GL::ClearColor(color.X, color.Y, color.Z, color.W);

	if (!!(clearMode & ClearOptions::Target))
	{
		clearBits |= GL_COLOR_BUFFER_BIT;
	}

	if (!!(clearMode & ClearOptions::DepthBuffer))
	{
		clearBits |= GL_DEPTH_BUFFER_BIT;
	}

	if (!!(clearMode & ClearOptions::Stencil))
	{
		clearBits |= GL_STENCIL_BUFFER_BIT;
	}

	if (clearBits != 0)
	{
		GL::Clear(clearBits);
	}
}

IRenderTarget2D* GLRenderDevice::CreateRenderTarget2D(const Math::Size& size)
{
	return static_cast<IRenderTarget2D*>(new GLRenderTarget2D(this, size, SurfaceFormat::Color, DepthFormat::None, RenderTargetUsage::DiscardContents));
}

IRenderTarget2D* GLRenderDevice::CreateRenderTarget2D(const Math::Size& size, SurfaceFormat surfaceFormat)
{
	return static_cast<IRenderTarget2D*>(new GLRenderTarget2D(this, size, surfaceFormat, DepthFormat::None, RenderTargetUsage::DiscardContents));
}

IRenderTarget2D* GLRenderDevice::CreateRenderTarget2D(const Math::Size& size, SurfaceFormat surfaceFormat, DepthFormat depthFormat)
{
	return static_cast<IRenderTarget2D*>(new GLRenderTarget2D(this, size, surfaceFormat, depthFormat, RenderTargetUsage::DiscardContents));
}

IRenderTarget2D* GLRenderDevice::CreateRenderTarget2D(const Math::Size& size, SurfaceFormat surfaceFormat, DepthFormat depthFormat, RenderTargetUsage usage)
{
	return static_cast<IRenderTarget2D*>(new GLRenderTarget2D(this, size, surfaceFormat, depthFormat, usage));
}

IVertexBuffer* GLRenderDevice::CreateVertexBuffer()
{
	return static_cast<IVertexBuffer*>(new GLVertexBuffer(this));
}

IIndexBuffer* GLRenderDevice::CreateIndexBuffer()
{
	return static_cast<IIndexBuffer*>(new GLIndexBuffer(this));
}

IVertexArray* GLRenderDevice::CreateVertexArray()
{
	return static_cast<IVertexArray*>(new GLVertexArray(this));
}

IEffect* GLRenderDevice::CreateEffect()
{
	return static_cast<IEffect*>(new GLEffect(this));
}

IEffect* GLRenderDevice::CreateEffect(const String& shader)
{
	return static_cast<IEffect*>(new GLEffect(this, shader));
}

Geometry* GLRenderDevice::CreateGeometry()
{
	return new Geometry(this);
}

Viewport* GLRenderDevice::CreateViewport(const Math::Rectangle& bounds)
{
	return new Viewport(this, bounds);
}

void GLRenderDevice::DrawIndexedPrimitives(PrimitiveType primitiveType, IVertexArray* vertexArray)
{
	GLenum mode;

	switch (primitiveType)
	{
		default:
		case PrimitiveType::TriangleList:	mode = GL_TRIANGLES;		break;
		case PrimitiveType::TriangleStrip:	mode = GL_TRIANGLE_STRIP;	break;
		case PrimitiveType::LineList:		mode = GL_LINES;			break;
		case PrimitiveType::LineStrip:		mode = GL_LINE_STRIP;		break;
	}

	vertexArray->Bind();
	GL::DrawElements(mode, vertexArray->AsConst()->GetIndexBuffer()->GetSize() / sizeof(u32), GL_UNSIGNED_INT, 0);  // const: we don't want to make VA dirty
}

GBuffer* GLRenderDevice::CreateGBuffer(const Math::Size& size)
{
	return new GBuffer(this, size);
}

void GLRenderDevice::SetRenderTarget(IRenderTarget2D* renderTarget)
{
	IRenderDevice::SetRenderTarget(renderTarget);

	if (renderTarget == nullptr)
	{
		this->SetNativeFramebuffer();
	}
	else
	{
		renderTarget->Bind();
	}
}

void GLRenderDevice::SetRenderTargets(std::initializer_list<IRenderTarget2D*> renderTargets)
{
	IRenderDevice::SetRenderTargets(renderTargets);

	if (renderTargets.size() == 0)
	{
		this->SetRenderTarget(nullptr);
		return;
	}

	GLuint framebufferHandle = this->GetRenderTargets().First()->As<GLRenderTarget2D*>()->GetFramebufferHandle();

	if (framebufferHandle == static_cast<GLuint>(-1))
	{
		framebufferHandle = GL::GenFramebuffer();
		GL::BindFramebuffer(framebufferHandle);

		u32 counter = 0;
		for (auto& rt : this->GetRenderTargets())
		{
			rt->As<GLRenderTarget2D*>()->SetFramebufferHandle(framebufferHandle, counter++);
		}
	}

	GL::BindFramebuffer(framebufferHandle);
	
	if (renderTargets.size() > 1)
	{
		GL::DrawBuffers(static_cast<GLsizei>(renderTargets.size()), Collections::List<GLenum>::FromEnumerable(Range<GLenum>(GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT0 + static_cast<GLenum>(renderTargets.size()))).GetDataPointer());
	}
}

void GLRenderDevice::SetNativeFramebuffer()
{
	GL::BindFramebuffer(0);
}

} } } } }
